package com.atguigu.gmall.search.service.impl;

import com.atguigu.gmall.pms.entity.BrandEntity;
import com.atguigu.gmall.pms.entity.CategoryEntity;
import com.atguigu.gmall.search.service.SearchService;
import com.atguigu.gmall.search.vo.Goods;
import com.atguigu.gmall.search.vo.QueryParamVo;
import com.atguigu.gmall.search.vo.SearchResponseAttrVo;
import com.atguigu.gmall.search.vo.SearchResponseVo;
import com.google.gson.Gson;
import io.jsonwebtoken.lang.Collections;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.RangeQueryBuilder;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.io.IOException;
import java.util.Date;
import java.util.List;

@Service
@Slf4j
public class SearchServiceImpl implements SearchService {

    @Autowired
    private RestHighLevelClient client;

    Gson gson = new Gson();

    @Override
    public SearchResponseVo search(QueryParamVo queryParamVo) {
        try {
            //1.创建请求对象
            SearchRequest request = new SearchRequest();//在这个对象其实就是封装 发给es请求 的请求报文
            request.indices("goods");//检索时使用的索引

            //2.编写DSL语句
            SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();//这个对象用来构建DSL语句的

            //布尔查询
            BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();

            //2.1标题关键字匹配查询
            String keyword = queryParamVo.getKeyword();
            if (StringUtils.isNotBlank(keyword)) {
                boolQueryBuilder.must(QueryBuilders.matchQuery("title", keyword));
            }

            //2.2品牌的过滤查询
            List<Long> brands = queryParamVo.getBrandId();
            if (!Collections.isEmpty(brands)) {
                boolQueryBuilder.filter(QueryBuilders.termsQuery("brandId", brands));
            }

            //2.3分类的过滤查询
            Long cid = queryParamVo.getCid();
            if (cid != null) {
                boolQueryBuilder.filter(QueryBuilders.termQuery("categoryId", cid));
            }

            //2.4价格区间的过滤查询
            Double priceFrom = queryParamVo.getPriceFrom();
            Double priceTo = queryParamVo.getPriceTo();
            if (priceFrom != null || priceTo != null) { //当priceFrom和priceTo任意一个有值，我们都进行范围查询
                RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery("price");
                if (priceFrom != null) {//priceFrom有值
                    rangeQuery.gte(priceFrom);
                }
                if (priceTo != null) {//priceTo有值
                    rangeQuery.lte(priceTo);
                }
                boolQueryBuilder.filter(rangeQuery);
            }

            //2.5库存的过滤判断
            Boolean store = queryParamVo.getStore();
            if (store != null) {
                boolQueryBuilder.filter(QueryBuilders.termQuery("store", store));
            }

            //2.6检索属性的过滤：嵌套类型操作
            List<String> props = queryParamVo.getProps();//props=4:8G-12G&props=5:128G-256G
            if (!Collections.isEmpty(props)) {
                for (String prop : props) {
                    String[] split = prop.split(":");
                    if (split != null && split.length == 2) {
                        String attrId = split[0];
                        String[] attrValues = split[1].split("-");
                        BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();
                        queryBuilder.must(QueryBuilders.termQuery("searchAttrs.attrId", attrId));
                        queryBuilder.must(QueryBuilders.termsQuery("searchAttrs.attrValue", attrValues));
                        boolQueryBuilder.filter(QueryBuilders.nestedQuery("searchAttrs", queryBuilder, ScoreMode.None));
                    }
                }
            }

            //2.7分页、排序、高亮、结果字段查询过滤
            //排序
            String orderName;
            SortOrder order;
            Integer sort = queryParamVo.getSort(); //0默认排序  1价格升序  2价格降序  3上架时间降序  4销量排序
            switch (sort) {
                case 1:
                    orderName = "price";
                    order = SortOrder.ASC;
                    break;
                case 2:
                    orderName = "price";
                    order = SortOrder.DESC;
                    break;
                case 3:
                    orderName = "createTime";
                    order = SortOrder.DESC;
                    break;
                case 4:
                    orderName = "sales";
                    order = SortOrder.DESC;
                    break;
                default:
                    orderName = "_score";
                    order = SortOrder.DESC; //默认使用_score分数值降序序
                    break;
            }
            sourceBuilder.sort(orderName, order);
            //分页，起始索引= （pageNum-1)*pageSize
            sourceBuilder.from((queryParamVo.getPageNum() - 1) * queryParamVo.getPageSize());//起始索引
            sourceBuilder.size(queryParamVo.getPageSize());//查询的记录条数
            //高亮
            sourceBuilder.highlighter(new HighlightBuilder().field("title").preTags("<font style='color:red'>").postTags("</font>"));
            //结果字段查询过滤
            sourceBuilder.fetchSource(new String[]{"skuId", "defaultImage", "price", "title", "subtitle"}, null);//以上字段会被查询到，其他的不会

            //2.8聚合
            //分类聚合
            sourceBuilder.aggregation(AggregationBuilders.terms("categoryIdAgg").field("categoryId")
                    .subAggregation(AggregationBuilders.terms("categoryNameAgg").field("categoryName")));
            //品牌聚合
            sourceBuilder.aggregation(AggregationBuilders.terms("brandIdAgg").field("brandId")
                    .subAggregation(AggregationBuilders.terms("brandNameAgg").field("brandName"))
                    .subAggregation(AggregationBuilders.terms("logoAgg").field("logo")));
            //属性和值的聚合：嵌套属性的聚合
            sourceBuilder.aggregation(AggregationBuilders.nested("attrAgg", "searchAttrs")
                    .subAggregation(AggregationBuilders.terms("attrIdAgg").field("searchAttrs.attrId")
                            .subAggregation(AggregationBuilders.terms("attrNameAgg").field("searchAttrs.attrName"))
                            .subAggregation(AggregationBuilders.terms("attrValueAgg").field("searchAttrs.attrValue"))));

            //3.向es发起请求
            sourceBuilder.query(boolQueryBuilder);
            log.info("{} 生成的DLS：{}", new Date(), sourceBuilder.toString());//{}表示占位符，第一个{}放new Date90,第二个放sourceBuilder.toString()
            request.source(sourceBuilder);
            SearchResponse response = client.search(request, RequestOptions.DEFAULT);

            //4.解析查询的结果，将结果封装为SearchResponseVo,发送给前端
            //4.1封装查询到的goods数据
            SearchResponseVo searchResponseVo = new SearchResponseVo();
            SearchHit[] hits = response.getHits().getHits();//结果是json字符串
            if (ArrayUtils.isNotEmpty(hits)) {
                for (SearchHit hit : hits) {
                    Goods goods = gson.fromJson(hit.getSourceAsString(), Goods.class);//将json字符串转化javaBean
                    //给goods设置高亮标题
                    HighlightField highlightField = hit.getHighlightFields().get("title");
                    if (highlightField != null && ArrayUtils.isNotEmpty(highlightField.getFragments())) {
                        String highLightTitle = StringUtils.join(highlightField.getFragments(), "");//StringUtils的join方法可以将数组的字符串进行拼接，分隔符为空
                        goods.setTitle(highLightTitle);
                    }
                    searchResponseVo.getGoodsList().add(goods);
                }
            }

            //4.2分页属性值
            searchResponseVo.setPageNum(queryParamVo.getPageNum());
            searchResponseVo.setPageSize(queryParamVo.getPageSize());
            searchResponseVo.setTotal(response.getHits().getTotalHits().value);//获取总记录条数

            //4.3聚合的结果-分类的聚合
            ParsedLongTerms categoryIdAgg = response.getAggregations().get("categoryIdAgg");//如果聚合时使用的属性值是字符串类型 返回的聚合类型是ParedStringTerms
            List<? extends Terms.Bucket> categoryIdAggBuckets = categoryIdAgg.getBuckets();
            if (!Collections.isEmpty(categoryIdAggBuckets)) {
                categoryIdAggBuckets.forEach(categoryIdAggBucket -> {
                    String categoryId = categoryIdAggBucket.getKey().toString();
                    ParsedStringTerms categoryNameAgg = categoryIdAggBucket.getAggregations().get("categoryNameAgg");//获取当前categoryId分类下的子桶
                    String categoryName = categoryNameAgg.getBuckets().get(0).getKey().toString();
                    CategoryEntity categoryEntity = new CategoryEntity();
                    categoryEntity.setId(Long.parseLong(categoryId));
                    categoryEntity.setName(categoryName);
                    searchResponseVo.getCategories().add(categoryEntity);
                });
            }
            //4.4聚合的结果-品牌的聚合
            ParsedLongTerms brandIdAgg = response.getAggregations().get("brandIdAgg");
            List<? extends Terms.Bucket> brandIdAggBuckets = brandIdAgg.getBuckets();
            if (!CollectionUtils.isEmpty(brandIdAggBuckets)) {
                brandIdAggBuckets.forEach(brandIdAggBucket -> {
                    String bId = brandIdAggBucket.getKey().toString();//获取brandId
                    ParsedStringTerms brandNameAgg = brandIdAggBucket.getAggregations().get("brandNameAgg");
                    ParsedStringTerms logoAgg = brandIdAggBucket.getAggregations().get("logoAgg");
                    String bName = brandNameAgg.getBuckets().get(0).getKey().toString();
                    String bLogo = logoAgg.getBuckets().get(0).getKey().toString();
                    BrandEntity brandEntity = new BrandEntity();
                    brandEntity.setLogo(bLogo);
                    brandEntity.setName(bName);
                    brandEntity.setId(bId);
                    searchResponseVo.getBrands().add(brandEntity);
                });
            }

            //4.5聚合的结果-嵌套属性的聚合和解析
            ParsedNested attrAgg = response.getAggregations().get("attrAgg");//拿到桶
            ParsedLongTerms attrIdAgg = attrAgg.getAggregations().get("attrIdAgg");
            List<? extends Terms.Bucket> attrIdAggBuckets = attrIdAgg.getBuckets();
            if (!CollectionUtils.isEmpty(attrIdAggBuckets)) {
                attrIdAggBuckets.forEach(attrIdAggBucket -> {
                    SearchResponseAttrVo attrVo = new SearchResponseAttrVo();//创建Vo对象
                    String attrId = attrIdAggBucket.getKey().toString();
                    attrVo.setAttrId(Long.parseLong(attrId));
                    //获取桶内的子聚合attrNameAgg
                    ParsedStringTerms attrNameAgg = attrIdAggBucket.getAggregations().get("attrNameAgg");
                    String attrName = attrNameAgg.getBuckets().get(0).getKey().toString();
                    attrVo.setAttrName(attrName);
                    //获取桶内的子聚合attrValueAgg
                    ParsedStringTerms attrValueAgg = attrIdAggBucket.getAggregations().get("attrValueAgg");
                    List<? extends Terms.Bucket> attrValueAggBuckets = attrValueAgg.getBuckets();
                    if (!CollectionUtils.isEmpty(attrValueAggBuckets)) {
                        attrValueAggBuckets.forEach(attrValueAggBucket -> {
                            String attrValue = attrValueAggBucket.getKey().toString();
                            attrVo.getAttrValues().add(attrValue);
                        });
                    }
                    searchResponseVo.getFilters().add(attrVo);
                });
            }

            return searchResponseVo;
            //System.out.println(searchResponseVo);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }

    }

}
